﻿namespace OverStore.Configuration.Validation
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Globalization;
    using System.Linq;

    /// <summary>
    /// Result of validation of component configuration
    /// </summary>
    public class ValidationResult
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ValidationResult"/> class.
        /// </summary>
        /// <param name="component">The validated component.</param>
        /// <exception cref="ArgumentNullException"><paramref name="component"/> is null reference.</exception>
        public ValidationResult(object component)
        {
            if (component == null)
                throw new ArgumentNullException("component");
            Component = component;
            Errors = new Collection<string>();
            NestedValidationResults = new Collection<ValidationResult>();
        }

        /// <summary>
        /// Get true if validation process does not find any errors, false otherwise.
        /// </summary>
        public bool IsValid
        {
            get
            {
                bool invalidNestedResultFound = false;
                foreach (var item in NestedValidationResults)
                {
                    if (!item.IsValid)
                    {
                        invalidNestedResultFound = true;
                        break;
                    }
                }

                return Errors.Count == 0 && !invalidNestedResultFound;
            }
        }

        /// <summary>
        /// Get component which was validated.
        /// </summary>
        public object Component { get; private set; }

        /// <summary>
        /// Get collection of errors found due validation of component configuration, or 
        /// empty sequence if no errors found.
        /// </summary>
        public ICollection<string> Errors { get; private set; }

        /// <summary>
        /// Get collection of <see cref="ValidationResult"/> for nested aggregated components.
        /// </summary>
        public ICollection<ValidationResult> NestedValidationResults { get; private set; }

        /// <summary>
        /// Return <see cref="ValidationResult"/> contains errors of current instance and also 
        /// specified error.
        /// </summary>
        /// <param name="error">Error to add in <see cref="ValidationResult.Errors"/> collection.</param>
        /// <returns><see cref="ValidationResult"/> contains errors of current instance plus specified error.</returns>
        public ValidationResult AddError(string error)
        {
            if (String.IsNullOrEmpty(error))
                throw new ArgumentNullException("error");
            Errors.Add(error);
            return this;
        }

        /// <summary>
        /// Return <see cref="ValidationResult"/> contains errors of current instance and also 
        /// specified error. Error message builds using format string and format parameters.
        /// </summary>
        /// <param name="format">The format string using for get error message.</param>
        /// <param name="formatArgs">The format arguments using for get error message.</param>
        /// <returns><see cref="ValidationResult"/> contains errors of current instance plus specified error.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="formatString"/> is null reference.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="formatArgs"/> is null reference.</exception>
        public ValidationResult AddError(string format, params object[] formatArgs)
        {
            if (format == null)
                throw new ArgumentNullException("format");
            if (formatArgs == null)
                throw new ArgumentNullException("formatArgs");
            return AddError(String.Format(CultureInfo.InvariantCulture, format, formatArgs));
        }

        /// <summary>
        /// If <paramref name="condition"/> is true, adds specified error.
        /// </summary>
        /// <param name="condition"><c>true</c> to add specified error; <c>false</c> otherwise.</param>
        /// <param name="error">The error message.</param>
        /// <returns><see cref="ValidationResult"/> contains specified errors.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="error"/> is null reference.</exception>
        public ValidationResult AddError(bool condition, string error)
        {
            if (condition)
                AddError(error);

            return this;
        }

        /// <summary>
        /// If <paramref name="condition"/> is true, adds specified error that builds using specified format string
        /// and format arguments.
        /// </summary>
        /// <param name="condition"><c>true</c> to add specified error; <c>false</c> otherwise.</param>
        /// <param name="format">The format string using for get error message.</param>
        /// <param name="formatArgs">The format arguments using for get error message.</param>
        /// <returns><see cref="ValidationResult"/> contains errors of current instance plus specified error.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="formatString"/> is null reference.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="formatArgs"/> is null reference.</exception>
        public ValidationResult AddError(bool condition, string format, params object[] formatArgs)
        {
            if (condition)
                AddError(format, formatArgs);
            return this;
        }

        /// <summary>
        /// Adds the nested result and return <see cref="ValidationResult"/> contains specified nested result. 
        /// </summary>
        /// <param name="result"><see cref="ValidationResult"/> to add it in <see cref="ValidationResult.NestedValidationResults"/> collection.</param>
        /// <returns><see cref="ValidationResult"/> contains with specified nested result added.</returns>
        /// <exception cref="ArgumentNullException">Result is null reference.</exception>
        public ValidationResult AddNestedResult(ValidationResult result)
        {
            if (result == null)
                throw new ArgumentNullException("result");
            NestedValidationResults.Add(result);
            return this;
        }

        /// <summary>
        /// If component is not null and implements <see cref="ISupportValidation"/> interface, 
        /// add result of validation of component.
        /// </summary>
        public ValidationResult ValidateToNestedResult(object component)
        {
            var sv = component as ISupportValidation;
            if (sv != null)
                NestedValidationResults.Add(sv.Validate());
            return this;
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return IsValid ? String.Format(CultureInfo.InvariantCulture, "[{0}] OK", Component) :
                String.Format(CultureInfo.InvariantCulture, "[{0}] {1}", Component, String.Join(", \r\n", Errors.ToArray()));
        }

        /// <summary>
        /// Gets the sequence of <see cref="ValidationResult"/> which are invalid (<see cref="ValidationResult.IsValid"/> is equal to <c>false</c>),
        /// including any nested invalid results.
        /// </summary>
        /// <returns>
        /// Sequence of <see cref="ValidationResult"/> which are invalid (<see cref="ValidationResult.IsValid"/> is equal to <c>false</c>),
        /// including any nested invalid results.
        /// </returns>
        public IEnumerable<ValidationResult> InvalidResults()
        {
            return Enumerable.Repeat(this, 1)
                .Where(r => r.Errors.Count() != 0).Concat(
                    NestedValidationResults.SelectMany(r => r.InvalidResults()));
        }
    }
}